package com.gitee.poi.plugin.core;

import com.gitee.poi.plugin.annotations.core.Association;
import com.gitee.poi.plugin.annotations.core.Column;
import com.gitee.poi.plugin.annotations.core.SheetName;
import com.gitee.poi.plugin.core.advice.AutoSizeColumnAdvice;
import com.gitee.poi.plugin.core.advice.DefaultExcelAdvice;
import com.gitee.poi.plugin.core.advice.ExcelAdvice;
import com.gitee.poi.plugin.core.handler.*;
import com.gitee.poi.plugin.core.handler.data.DateFormatProcessor;
import com.gitee.poi.plugin.core.handler.data.DefaultProcessor;
import com.gitee.poi.plugin.core.handler.data.HandlerDataAnnotation;
import com.gitee.poi.plugin.core.handler.data.TransformProcessor;
import com.gitee.poi.plugin.core.write.DefaultWriteCellValue;
import com.gitee.poi.plugin.core.write.WriteCellValue;
import com.gitee.poi.plugin.model.CellModel;
import com.gitee.poi.plugin.model.FieldStyle;
import com.gitee.poi.plugin.model.SheetInfo;
import com.gitee.poi.plugin.util.ClassUtils;
import com.gitee.poi.plugin.util.CommonUtils;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public class ExcelProcessor {

    private final static Logger logger = LoggerFactory.getLogger(ExcelProcessor.class);

    private static Map<Field, HandlerDataAnnotation> cacheHandlerDataAnnotations = new ConcurrentHashMap<>();

    private static final HandlerSheetLastRow DEFAULT_HANDLER_SHEET_LAST_ROW = new DefaultHandlerSheetLastRow();
    //默认的导入时间转换格式
    private static final String DEFAULT_DATE_FORMAT = "yyyy/MM/dd";

    //默认写入数据的处理类
    private WriteCellValue writeCellValue = new DefaultWriteCellValue();

    //限制Excel读取最大行数，默认-1 不做限制
    private Integer allowReadExcelMaxRow = -1;

    //通知
    private List<ExcelAdvice> excelAdvices;

    //自定义通知
    private List<ExcelAdvice> customExcelAdvices;

    private List<HandlerAnnotation> columnHandlerAnnotations;

    private List<HandlerAnnotation> handlerAnnotations;

    //自定义列标题处理器
    private List<HandlerAnnotation> customColumnHandlerAnnotations;

    //自定义列处理器
    private List<HandlerAnnotation> customHandlerAnnotations;

    private List<HandlerDataAnnotation> handlerDataAnnotations;

    private List<HandlerDataAnnotation> customHandlerDataAnnotations;

    /**
     * 决定用XSSF还是HSSF
     */
    private boolean compatibility = true;

    public ExcelProcessor() {

        excelAdvices = new ArrayList<>(3);
        excelAdvices.add(new DefaultExcelAdvice());
        excelAdvices.add(new AutoSizeColumnAdvice());

        //自定义列处理器
        handlerAnnotations = new ArrayList<>(15);
        handlerAnnotations.add(new FontProcessor());
        handlerAnnotations.add(new FontNameProcessor());
        handlerAnnotations.add(new BoldProcessor());
        handlerAnnotations.add(new ItalicProcessor());
        handlerAnnotations.add(new StrikeoutProcessor());
        handlerAnnotations.add(new ColorProcessor());
        handlerAnnotations.add(new FontSizeProcessor());
        handlerAnnotations.add(new ShrinkToFitProcessor());
        handlerAnnotations.add(new UnderLineProcessor());
        handlerAnnotations.add(new AlignProcessor());
        handlerAnnotations.add(new WarpTextProcessor());
        handlerAnnotations.add(new VerticalAlignProcessor());
        handlerAnnotations.add(new DataFormatProcessor());

        //自定义列标题处理器
        columnHandlerAnnotations = new ArrayList<>(3);
        columnHandlerAnnotations.add(new ColumnProcessor());

        handlerDataAnnotations = new ArrayList<>(8);
        handlerDataAnnotations.add(new TransformProcessor());
        handlerDataAnnotations.add(new DateFormatProcessor());
        //该处理器始终放在最后一个
        handlerDataAnnotations.add(new DefaultProcessor());
    }

    private Workbook createWorkBook() {
        if (compatibility) {
            return new HSSFWorkbook();
        }
        return new XSSFWorkbook();
    }

    @PostConstruct
    public void mergeHandlerProcessor() {
        if (customExcelAdvices != null) {
            excelAdvices.addAll(customExcelAdvices);
        }
        if (customHandlerAnnotations != null) {
            handlerAnnotations.addAll(customHandlerAnnotations);
        }
        if (customColumnHandlerAnnotations != null) {
            columnHandlerAnnotations.addAll(customColumnHandlerAnnotations);
        }
        if (customHandlerDataAnnotations != null) {
            handlerDataAnnotations.addAll(handlerDataAnnotations.size() - 1, customHandlerDataAnnotations);
        }
    }

    private boolean isExistSheet(String name, Workbook wb) {
        return wb.getSheet(name) != null;
    }

    public String getSheetName(Class clazz, Workbook wb) {
        SheetName sheetName = (SheetName) clazz.getAnnotation(SheetName.class);
        String name = sheetName.value();
        while (isExistSheet(name, wb)) {
            name = name + "_";
        }
        return name;
    }

    public Workbook getWorkBook(List<?>... lists) {
        Workbook wb = createWorkBook();
        for (int i = 0; i < lists.length; i++) {
            List<?> list = lists[i];
            if (list != null && list.size() > 0) {
                Class clazz = list.get(0).getClass();
                Sheet summary = wb.createSheet(getSheetName(clazz, wb));
                processData(list, clazz, summary);
            }
        }
        return wb;
    }

    /**
     * k sheet名称 v sheet数据<br>
     * {@link SheetName} 注解将无效，此处始终使用 k 值
     *
     * @param map
     * @return
     */
    public Workbook getWorkBook(Map<String, ? extends List> map) {
        Workbook wb = createWorkBook();
        Optional.ofNullable(map).ifPresent(m -> m.forEach((k, v) -> {
            List<?> list = v;
            if (list != null && list.size() > 0) {
                Class clazz = list.get(0).getClass();
                Sheet summary = wb.createSheet(k);
                processData(list, clazz, summary);
            }
        }));
        return wb;
    }

    protected void processData(List<?> list, Class clazz, Sheet summary) {
        List<FieldStyle> fields = getFields(clazz, summary);
        int rowNumber = 0;
        if (excelAdvices != null) {
            SheetInfo sheetInfo = new SheetInfo(summary, clazz, fields, rowNumber);
            for (int index = 0, length = excelAdvices.size(); index < length; index++) {
                ExcelAdvice excelAdvice = excelAdvices.get(index);
                sheetInfo = excelAdvice.beforeWrite(sheetInfo);
            }
            rowNumber = sheetInfo.getCurrentRowIndex();
        }
        Row rowTitle = summary.createRow(rowNumber);
        //写入头部
        for (int fieldIndex = 0, length = fields.size(); fieldIndex < length; fieldIndex++) {
            Cell cell_i = rowTitle.createCell(fieldIndex);
            cell_i.setCellStyle(fields.get(fieldIndex).getColumnStyle());
            cell_i.setCellValue(fields.get(fieldIndex).getField().getAnnotation(Column.class).value());
        }

        //写入具体数据
        for (int index = 0, length = list.size(); index < length; index++) {
            Object item = list.get(index);
            rowNumber += 1;
            Row row_i = summary.createRow(rowNumber);
            for (int fieldIndex = 0, fieldsLength = fields.size(); fieldIndex < fieldsLength; fieldIndex++) {
                try {
                    Cell cell_i = row_i.createCell(fieldIndex);
                    Field field = fields.get(fieldIndex).getField();
                    CellStyle style = fields.get(fieldIndex).getStyle();
                    CellModel cellModel = new CellModel(field, item, field.get(item), style, summary, rowNumber, row_i, fieldIndex, cell_i, fields,writeCellValue);
                    cellModel = getCellValueAndStyle(cellModel);
                    rowNumber = cellModel.getCurrentRowIndex();
                } catch (Exception e) {
                    try {
                        summary.getWorkbook().close();
                    } catch (IOException e1) {
                        logger.error("workbook关闭异常");
                    }
                    throw new IllegalArgumentException(e);
                }
            }
        }
        rowNumber += 1;
        if (excelAdvices != null) {
            SheetInfo sheetInfo = new SheetInfo(summary, clazz, fields, rowNumber);
            for (int index = 0, length = excelAdvices.size(); index < length; index++) {
                ExcelAdvice excelAdvice = excelAdvices.get(index);
                sheetInfo = excelAdvice.afterWrite(sheetInfo);
            }
        }
    }

    private CellModel getCellValueAndStyle(CellModel cellModel) {

        Field field = cellModel.getField();
        HandlerDataAnnotation handlerDataAnnotation = cacheHandlerDataAnnotations.get(field);
        if (handlerDataAnnotation != null) {
            return handlerDataAnnotation.process(cellModel);
        }
        for (int i = 0; i < handlerDataAnnotations.size(); i++) {
            handlerDataAnnotation = handlerDataAnnotations.get(i);
            if (handlerDataAnnotation.support(field)) {
                cacheHandlerDataAnnotations.put(field, handlerDataAnnotation);
                return handlerDataAnnotation.process(cellModel);
            }
        }
        return cellModel;
    }
    public <T> List<T> getList(Workbook wb, Class<T> clazz) {
        return  getList(wb,clazz,DEFAULT_HANDLER_SHEET_LAST_ROW,DEFAULT_DATE_FORMAT);
    }

    public <T> List<T> getList(Workbook wb, Class<T> clazz, HandlerSheetLastRow handlerSheetLastRow) {
        return  getList(wb,clazz,handlerSheetLastRow,DEFAULT_DATE_FORMAT);
    }

    public <T> List<T> getList(Workbook wb, Class<T> clazz, String dateFormatStr) {
        return  getList(wb,clazz,DEFAULT_HANDLER_SHEET_LAST_ROW,dateFormatStr);
    }

    public <T> List<T> getList(Workbook wb, Class<T> clazz, HandlerSheetLastRow handlerSheetLastRow, String dateFormatStr){
        List<T> list = new ArrayList<>();
        Sheet sheet = wb.getSheetAt(0); //目前只支持单页Excel导入
        List<FieldStyle> fieldStyles = getFields(clazz, sheet);
        int maxRow = handlerSheetLastRow.getLastRow(sheet);
        if (allowReadExcelMaxRow != -1 && maxRow >= allowReadExcelMaxRow) {
            throw new IllegalArgumentException("你的Excel格式非法，超出了允许的最大行数");
        }
        int maxColumn = fieldStyles.size();
        List<FieldStyle> adaptFieldStyles = new ArrayList<>(maxColumn);
        Row headerRow = sheet.getRow(0);
        //校验Excel列头是否符合规范
        for (int cellIndex = 0; cellIndex < maxColumn; cellIndex++) {
            Object obj = getCellValue(headerRow.getCell(cellIndex));
            for (int i = 0; i < maxColumn; i++) {
                FieldStyle fieldStyle = fieldStyles.get(i);
                Column column = fieldStyle.getField().getAnnotation(Column.class);
                if (column.value().equals(obj)) {
                    adaptFieldStyles.add(fieldStyle);
                    break;
                }
            }
            if (adaptFieldStyles.size() - 1 != cellIndex) {
                throw new IllegalArgumentException("你的Excel格式非法，未找到列标题" + obj + "，请检查Excel标题!");
            }
        }

        for (int rowIndex = 1; rowIndex <= maxRow; rowIndex++) { //注意maxRow可以取等
            Row row = sheet.getRow(rowIndex);
            T t ;
            try {
                t = clazz.newInstance();
            } catch (Exception e) {
                throw new IllegalArgumentException(clazz.getName()+"无法构建对象，请检查其是否有无参构造函数和其访问权限");
            }
            for (int columnIndex = 0; columnIndex < maxColumn; columnIndex++) {
                try {
                    Object obj = getCellValue(row.getCell(columnIndex));
                    Field field = adaptFieldStyles.get(columnIndex).getField();
                    Class<?> fieldClass = field.getType();
                    field.set(t, convertValue(fieldClass, obj, dateFormatStr));
                } catch (Exception e) {
                    throw new IllegalArgumentException(e.getMessage() + ":位置行：" + (rowIndex + 1) + " 位置列：" + (columnIndex + 1),e);
                }
            }
            list.add(t);
        }
        return list;
    }

    private <T> Object convertValue(Class<T> clazz, Object value,String dateFormatStr) throws ParseException {
        if(value == null) return null;
        T convertValue;
        if (clazz.isAssignableFrom(value.getClass())) {
            convertValue = (T) value;
        } else if (clazz == String.class) {
            convertValue = (T) value.toString();
        } else if (clazz == Date.class) {
            if (value.getClass() == String.class) {
                DateFormat dateFormat = new SimpleDateFormat(dateFormatStr);
                convertValue = (T) dateFormat.parse(value.toString());
            } else if (value.getClass() == BigDecimal.class) {
                return DateUtil.getJavaDate(Double.parseDouble(value.toString()), false);
            } else {
                throw new IllegalArgumentException("时间格式不对！");
            }
        } else if (clazz == BigDecimal.class) {
            convertValue = (T) new BigDecimal(value.toString());
        } else if (clazz == BigInteger.class) {
            convertValue = (T) new BigInteger(value.toString());
        } else if (clazz == int.class || clazz == Integer.class) {
            return Integer.parseInt(value.toString());
        } else if (clazz == double.class || clazz == Double.class) {
            return Double.parseDouble(value.toString());
        } else if (clazz == boolean.class || clazz == Boolean.class) {
            return Boolean.valueOf(value.toString());
        } else {
            throw new IllegalArgumentException("不能识别该类型，数据格式不对！");
        }
        return convertValue;
    }

    private Object getCellValue(Cell cell) {
        Object obj = null;
        if (cell.getCellType() == CellType.BLANK) {
//            throw new IllegalArgumentException("非法访问，不允许出现列值为空");
            return null;
        } else if (cell.getCellType() == CellType.BOOLEAN) {
            obj = cell.getBooleanCellValue();
        } else if (cell.getCellType() == CellType.NUMERIC) {
            //转换成String类型，防止double转bigDecimal出现很多小数问题
            double value = cell.getNumericCellValue();
            if (CommonUtils.isFloat(value)) {
                obj = new BigDecimal(String.valueOf(cell.getNumericCellValue()));
            } else {
                obj = new BigDecimal(value);
            }
        } else if (cell.getCellType() == CellType.STRING) {
            obj = cell.getStringCellValue().trim();
        } else if (cell.getCellType() == CellType.ERROR) {
            throw new IllegalArgumentException("错误的Excel列值");
        } else if (cell.getCellType() == CellType.FORMULA) {
            obj = cell.getCellFormula();
        }
        return obj;
    }

    private CellStyle getStyle(Sheet sheet, Field field, List<HandlerAnnotation> handlerAnnotations) {
        CellStyle style = sheet.getWorkbook().createCellStyle();
        Font font = sheet.getWorkbook().createFont();
        style.setFont(font);
        for (HandlerAnnotation handler : handlerAnnotations) {
            if (handler.supportsAnnotation(field)) {
                style = handler.process(font, style, sheet, field);
            }
        }
        return style;
    }

    private List<FieldStyle> getFields(Class clazz, Sheet sheet) {
        Field[] allFields = ClassUtils.getAllField(clazz);
        List<FieldStyle> fields = new ArrayList<>(allFields.length);
        //过滤掉不需要的列
        for (int index = 0; index < allFields.length; index++) {
            Column column = allFields[index].getAnnotation(Column.class);
            if (column != null) {
                //设置访问权限
                allFields[index].setAccessible(true);
                //取得列标题样式
                CellStyle columnStyle = getStyle(sheet, allFields[index], columnHandlerAnnotations);
                //取得列样式
                CellStyle style = getStyle(sheet, allFields[index], handlerAnnotations);
                FieldStyle fieldStyle = new FieldStyle();
                fieldStyle.setField(allFields[index]);
                fieldStyle.setStyle(style);
                fieldStyle.setColumnStyle(columnStyle);
                fields.add(fieldStyle);
            }
        }
        //排序
        Collections.sort(fields, (f1, f2) -> f1.getField().getAnnotation(Column.class).order() < f2.getField().getAnnotation(Column.class).order() ? -1 : 1);
        return fields;
    }

    public WriteCellValue getWriteCellValue() {
        return writeCellValue;
    }

    public void setWriteCellValue(WriteCellValue writeCellValue) {
        this.writeCellValue = writeCellValue;
    }

    public boolean isCompatibility() {
        return compatibility;
    }

    public void setCompatibility(boolean compatibility) {
        this.compatibility = compatibility;
    }

    public List<HandlerAnnotation> getHandlerAnnotations() {
        return handlerAnnotations;
    }

    public List<HandlerAnnotation> getColumnHandlerAnnotations() {
        return columnHandlerAnnotations;
    }

    public List<HandlerAnnotation> getCustomColumnHandlerAnnotations() {
        return customColumnHandlerAnnotations;
    }

    public List<HandlerDataAnnotation> getHandlerDataAnnotations() {
        return handlerDataAnnotations;
    }

    public List<HandlerDataAnnotation> getCustomHandlerDataAnnotations() {
        return customHandlerDataAnnotations;
    }

    public void setCustomColumnHandlerAnnotations(List<HandlerAnnotation> customColumnHandlerAnnotations) {
        this.customColumnHandlerAnnotations = customColumnHandlerAnnotations;
    }

    public List<HandlerAnnotation> getCustomHandlerAnnotations() {
        return customHandlerAnnotations;
    }

    public void setCustomHandlerAnnotations(List<HandlerAnnotation> customHandlerAnnotations) {
        this.customHandlerAnnotations = customHandlerAnnotations;
    }

    public List<ExcelAdvice> getExcelAdvices() {
        return excelAdvices;
    }

    public List<ExcelAdvice> getCustomExcelAdvices() {
        return customExcelAdvices;
    }

    public void setCustomExcelAdvices(List<ExcelAdvice> customExcelAdvices) {
        this.customExcelAdvices = customExcelAdvices;
    }

    public Integer getAllowReadExcelMaxRow() {
        return allowReadExcelMaxRow;
    }

    public void setAllowReadExcelMaxRow(Integer allowReadExcelMaxRow) {
        this.allowReadExcelMaxRow = allowReadExcelMaxRow;
    }

    public void setCustomHandlerDataAnnotations(List<HandlerDataAnnotation> customHandlerDataAnnotations) {
        this.customHandlerDataAnnotations = customHandlerDataAnnotations;
    }

}
